package com.gzsxy.esjy.decoder.impl;

import com.alibaba.fastjson.JSON;
import com.gzsxy.esjy.SvConstant;
import com.gzsxy.esjy.common.Assert;
import com.gzsxy.esjy.configuration.NettyServerConfig;
import com.gzsxy.esjy.configuration.properties.ServerProperties;
import com.gzsxy.esjy.decoder.BaseMessageDecoder;
import com.gzsxy.esjy.extraldecoder.IBusinessDecoder;
import com.gzsxy.esjy.filter.FilterResult;
import com.gzsxy.esjy.filter.Ifilter;
import com.gzsxy.esjy.handler.BaseHandler;
import com.gzsxy.esjy.handler.HttpHandler;
import com.gzsxy.esjy.mapping.MappingEntity;
import com.gzsxy.esjy.mapping.MappingInit;
import com.gzsxy.esjy.request.ContentType;
import com.gzsxy.esjy.request.HttpServletRequest;
import com.gzsxy.esjy.request.RequestMessage;
import com.gzsxy.esjy.request.impl.BaseRequest;
import com.gzsxy.esjy.request.impl.DefaultHttpServletRequest;
import com.gzsxy.esjy.request.impl.DefaultMultipartHttpRequest;
import com.gzsxy.esjy.response.NettyHttpResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import org.springframework.util.StringUtils;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * http解码器 BEAN
 *
 * @author zhangxiaolong
 * @date 2022/7/6 9:36
 */
public class HttpRequestDecoder extends BaseMessageDecoder<HttpRequest> {
    //DefaultHttpDataFactory.MINSIZE这个策略会导致一个问题，就是form-data传过来的文件，如果小于16KB，就单纯的当做ByteBf来处理，并写入内存中,会过滤掉16K以下的文件。FileUpload对象无法识别File这个对象，大于16kb将写入磁盘
	//DefaultHttpDataFactory.MAXSIZE这个策略是只要是文件,就会识别为File对象,
	private static final HttpDataFactory FACTORY = new DefaultHttpDataFactory(DefaultHttpDataFactory.MAXSIZE);

	private final MappingInit mappingInit;

	private final Ifilter ifilter;

	private final NettyServerConfig nettyServerConfig ;

	private final HttpHandler httpHandler;

	private final ServerProperties config;
	/**
	 * 移除springboot
	 */
	private static final String FAVICON_ICO = "/favicon.ico";
	
	private HttpPostRequestDecoder decoder ;

	public HttpRequestDecoder(MappingInit mappingInit, Ifilter ifilter, NettyServerConfig nettyServerConfig, HttpHandler httpHandler, ServerProperties config) {
		super();
		this.mappingInit = mappingInit;
		this.ifilter = ifilter;
		this.nettyServerConfig = nettyServerConfig ;
		this.httpHandler = httpHandler;
		this.config = config;
	}

	@SuppressWarnings("all")
	@Override
	protected void decode(ChannelHandlerContext ctx, HttpRequest request, List<Object> out) {
		HttpServletRequest fullrequest = null;
		Object next = request;
		FilterResult result = null;
		try {
			//输出头部日志
			HttpHeaders headers = request.headers();
			List<Entry<String, String>> entries = headers.entries();
			StringBuilder builder = new StringBuilder() ;
			entries.forEach(entry->builder.append("\n - ").append(entry.getKey()).append("=").append(entry.getValue())
			);
			log.info("request {} {}",JSON.toJSON(request),builder);
			
			//判断是否解码成功
			Assert.notTrue(request.decoderResult().isSuccess(), HttpResponseStatus.BAD_REQUEST.toString(),
					HttpResponseStatus.BAD_REQUEST.code());
			//过滤(暂无)
			if (ifilter != null) {
				result = new FilterResult();
				ifilter.doFilter(request.uri(), request.method(), request.headers(), ctx.channel().remoteAddress().toString(), result);
				if (!result.isContinue()) {
					ctx.writeAndFlush(result.getResponse());
					return;
				}
			}
            //过滤图标
			if (FAVICON_ICO.equals(request.uri())) {
				return;
			}
			
			// 特殊处理请求--
			if(nettyServerConfig != null) {
				List<IBusinessDecoder> list = nettyServerConfig.decoders();
				for (IBusinessDecoder iBusinessDecoder : list) {
					BaseHandler ready = iBusinessDecoder.ready(ctx.channel(), request) ;
					//如果该请求为文件上传则为空，否则文件浏览等等不为空
					if(ready != null) {
						BaseHandler baseHandler = ctx.channel().pipeline().get(ready.getClass());
						if(baseHandler == null) {
							//添加文件处理器
							ctx.channel().pipeline().addLast(ready);
						}
						next = ready.getContent();
						ready.setContent(null);
						return ;
					}
				}
			}
			
			// http请求统一处理
			HttpHandler http = ctx.channel().pipeline().get(HttpHandler.class);
			if (http == null) {
				ctx.channel().pipeline().addLast(httpHandler);
			}
			
			//判断是否文件上传
			RequestMessage requestMessage = RequestMessage.createRequestMessage(request);
			log.info("构建http解码器 {}",request.uri());
			//Post 协议分为两种，一种是普通post协议，一种是 multipart/form-data类型。
			//netty解析http协议，会将解析后的数据
			decoder = new HttpPostRequestDecoder(FACTORY, request);
			//不为上传文件
			if (!decoder.isMultipart()) {
				//获取报文长度
				long contentLength = requestMessage.getContentLength();
				Assert.notTrue(contentLength < SvConstant.MAX_LENGTH, String.format("请求报文过大 limit:%s actually:%s", SvConstant.MAX_LENGTH,contentLength), 400);
				fullrequest = new DefaultHttpServletRequest(request.protocolVersion(), request.method(), request.uri(),
						request.headers(), request.decoderResult(), requestMessage.getContentLength(),
						requestMessage.getContentType(), Charset.forName(requestMessage.getCharset()));
				decoder.destroy();
				decoder = null ;
			} else {
				//处理文件上传
				decoder.cleanFiles();
				fullrequest = new DefaultMultipartHttpRequest(request.protocolVersion(), request.method(),
						request.uri(), request.headers(), request.decoderResult(), decoder,
						requestMessage.getContentType(), Charset.forName(requestMessage.getCharset()));
			}

			checkRequest(fullrequest, result,  ctx.channel().remoteAddress().toString());
			//赋值当前fullrequest对象给status-halder处理器
			request(ctx, fullrequest);
		} catch (Exception e) {
			FullHttpResponse doExceptionResponse = NettyHttpResponse.doExceptionResponse(e,
					fullrequest == null ? "utf-8" : fullrequest.getCharset().name());
			ctx.writeAndFlush(doExceptionResponse);
		} finally {
			out.add(next);
		}
	}

	/**
	 * 判断是否上传文件
	 * 
	 * @param requestMessage
	 * @return
	 */
	private boolean isUpload(RequestMessage requestMessage) {
		if (requestMessage.getMethod() == HttpMethod.POST
				&& requestMessage.getContentType() == ContentType.MULTIPART_FORM_DATA) {
			return true;
		}
		return false;
	}

	/**
	 * 检查请求是否合法
	 * 
	 * @param result
	 * 
	 * @throws Exception
	 */
	private void checkRequest(HttpServletRequest request, FilterResult result, String remoteAddress) {
		//解析参数
		String uri = parseParam(request);
		String path = MappingInit.checkPath(uri, request.getParams());
		Assert.isNotNull(path, String.format("uri[%s] is not found ", uri), 404);
		MappingEntity mapping = mappingInit.getMapping(path);
		Assert.isNotNull(path, String.format("uri[%s] is not found ", uri), 404);
		List<HttpMethod> supportMotheds = mapping.getSupportMotheds();
		Assert.isNotNull(supportMotheds, String.format("uri[%s] is not found ", uri), 404);
		if (supportMotheds.size() > 0) {
			Assert.notTrue(supportMotheds.contains(request.getMethod()),
					String.format("method '%s' is not support in uri[%s] ", request.getMethod().name(), uri), 400);
		}
		BaseRequest base = ((BaseRequest) request).setMappingEntity(mapping).setPath(path)
				.setKeepAlive(isKeepAlive(request)).setRemoteAddress(remoteAddress.toString());
		if (result != null) {
			base.setRemoteUser(result.getRemoteUser());
		}
	}

	/**
	 * 判断是否长连接
	 * 
	 * @param request
	 * @return
	 */
	private boolean isKeepAlive(HttpServletRequest request) {
		if(!config.isKeepLive()) {
			return false ;
		}
		String connection = request.getHeaders().get(HttpHeaderNames.CONNECTION);
		if (connection != null && HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(connection)) {
			return false;
		}

		if (request.getProtocolVersion().isKeepAliveDefault()) {
			return !HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(connection);
		} else {
			return HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(connection);
		}
	}

	/**
	 * 解析参数
	 * 
	 * @param request
	 */
	private String parseParam(HttpServletRequest request) {
		QueryStringDecoder decoder = new QueryStringDecoder(request.getUri());
		Map<String, List<String>> parameters = decoder.parameters();
		for (Entry<String, List<String>> attr : parameters.entrySet()) {
			for (String attrVal : attr.getValue()) {
				if (StringUtils.hasText(attrVal)) {
					request.getParams().put(attr.getKey(), attrVal);
				}
			}
		}
		return decoder.path();
	}
	
	
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		ctx.fireChannelInactive();
		if(decoder != null) {
			try {
				decoder.cleanFiles();
			}catch(Exception e){}finally {
				decoder = null ;
			}
		}
	}

}
